package com.netflix.eventbus.spi;

import javax.annotation.Nullable;


import java.util.Set;
import java.util.concurrent.BlockingQueue;

/**
 * An event bus for <em>in-process</em> publish-subscribe communication between components.
 * It aids in de-coupling publishers and subscribers and provides a simple way of publishing, filtering and listening
 * to events.
 * 
 * The various facets of the event bus is defined as follows:
 *
 * <h2>Publishers</h2> Publisher of events can publish the events in two modes as follows:
 * <ul>
 <li>Unconditional: The event gets published irrespective of the fact whether there are any listeners or not.
 If there are no listeners, the event will be discarded.</li>
 <li>Conditional: The event will be published iff there is atleast one listener for that event at the time
 publish is called. This style should be used only when creating the event object itself is costly, otherwise,
 the complexity is not worth the effort. In this case, the event creation is delayed using {@link EventCreator}.
 In case, a listener that showed interest in the event unregisters before receiving the same, it will not receive this
 event. On the other hand, if any new listener gets registered for this event after the initial check is made, it will
 receive this event, provided it gets registered before the event starts to get published.
 The interest of the listener for this event will only be based on the event type and not the event data as the data is
 not available initially. This essentially means that such an event may be filtered-out by the listener based on the
 event data when it actually receives the event.</li>
 </ul>
 *
 * <h2>Events</h2> An event can be any arbitrary object and does not require any semantics.
 *
 * <h2>Subscribers</h2> Subscribers are the components that are interested in events generated by the publishers. A
 * subscriber requests interest in certain events by providing a method with the annotation
 * {@link Subscribe} and having just one argument, the event object itself.
 *
 * <h3>Event - Subscriber mapping</h3> Any subscriber method taking an event object of Class X as argument is expected
 * to be interested in all events published corresponding to its superclasses and implemented interface (directly or via
 * a superclass). In short if the invocation of {@link Class#isAssignableFrom(Class)} on the event object's class with
 * the subscriber class as argument returns <code>true</code>, then that subscriber is interested in the event.
 *
 * <h3>Dispatch mode</h3> <em>All subscribers are async</em> and isolated with any other listener i.e. a slow listener
 * does not impact other listeners for the same event. At the same time, the event data is not copied for this isolation.
 * The overhead of this isolation will just be the cost of storing references to these events in each listeners queue.
 *
 * <b>A subscriber can indicate if it favors synchronous event consumption: </b> This is just a backdoor and should be
 * used with caution as then the consumption and filtering will happen in the publishing thread. Also, this behavior can
 * be overridden if the property {@link SyncSubscribersGatekeeper#ALLOW_SYNC_SUBSCRIBERS} is set to <code>false</code>. <b>In that case, the
 * events will be sent to the subscriber asynchronously. See {@link SyncSubscribersGatekeeper} for more details.</b>
 *
 * There are multiple facets of this async dispatch mode which are described below:
 * <ul>
    <li><i>Slow subscribers:</i> In case the subscriber queue is full, the older events are rejected, one at a time and
 the event bus retries to offer the event to the subscriber, after each removal. This retry is capped at a default value
 {@link EventBus#CONSUMER_QUEUE_FULL_RETRY_MAX_DEFAULT} which can be overridden by a dynamic property:
 {@link EventBus#CONSUMER_QUEUE_FULL_RETRY_MAX_PROP_NAME}. If all the retries fail, the event is rejected.</li>
    <li><i>Event batch:</i> If the subscribers wish to process the events in batches, then they can annotate themselves with
 an appropriate {@link com.netflix.eventbus.spi.Subscribe.BatchingStrategy}. Care must be taken while batching as the
 rejections also happens in batch when the subscribers are slow.</li>
    <li><i>Queue size:</i> A subscriber can optionally define a queue size for the async dispatch using
 {@link com.netflix.eventbus.spi.Subscribe#queueSize()}. The default value for this queue size is
 {@link EventBus#CONSUMER_QUEUE_SIZE_DEFAULT} which can be changed via the fast property:
 {@link EventBus#CONSUMER_QUEUE_SIZE_DEFAULT_PROP_NAME}</li>
 </ul>
 *
 * <h2>Event filtering</h2> Events can be filtered both before publishing and before receiving the same in the
 * subscriber. Filtering at the subscriber end is done per subscriber. Filtering at the publisher side rejects the event.
 *
 * <b>All filters for the event must return true for the event to be published/received</b>
 * Event filters can be expressed as a filter language as available under {@link com.netflix.eventbus.filter.lang}.
 * Such a filter can then be converted into an {@link EventFilter} using {@link com.netflix.eventbus.filter.EventFilterCompiler}
 * For relatively easy way of programmatic creation of filters.
 *
 * Subscriber level filtering is done in the consumer thread and not in the thread of the event publisher. This
 * alleviates the overhead of event publishing that may be introduced by slow filter evaluation. However, it does
 * have a side effect on the speed of event processing by the consumer and hence may increase the backlog for particular
 * consumers. In any case, the impact of slow filters is isolated to the consumers and does not plague the event
 * publishing that typically will happen in the client's request processing thread.
 *
 * <h2>Runtime event - subscriber binding</h2>
 *
 * Eventbus subscribers, by design, are statically tied to a particular type of object i.e. the class of the event it
 * is interested in. This in most cases is beneficial and easy to use, however, in some cases (typically event agnostic,
 * middlemen, which stores the event for later investigation), the event processing is much the same irrespective of the
 * type of event it receives. In such a case, it will be an overhead (even if possible) to define a subscriber for
 * each kind of event existing in the system. {@link DynamicSubscriber} is a way to achieve runtime binding of an event
 * to a subscriber.
 *
 * @author Nitesh Kant (nkant@netflix.com)
 */
public interface EventBus {

    public static final String CONSUMER_QUEUE_FULL_RETRY_MAX_PROP_NAME = "eventbus.consumer.queuefull.maxretries";
    public static final int CONSUMER_QUEUE_FULL_RETRY_MAX_DEFAULT = 5;

    public static final String CONSUMER_QUEUE_SIZE_DEFAULT_PROP_NAME = "eventbus.consumer.queue.size.default";
    public static final int CONSUMER_QUEUE_SIZE_DEFAULT = 1000;

    /**
     * Publishes the passed event object. The following is done on receiving this call:
     * <ul>
     <li>Apply all publisher level filters for this event. If any of the filter returns <code>false</code> then exit.</li>
     <li>Find all interested subscribers.</li>
     <li>Apply filters per interested subscriber.</li>
     <li>For the subscribers that pass the filtering criterion, enqueue the event to the subscriber's queue (async dispatch)</li>
     </ul>
     *
     * @param event Event to publish.
     */
    void publish(Object event);

    /**
     * Publishes events iff there is atleast one listener for any of the passed event types. See {@link EventBus}
     * javadocs for details about conditional event publishing.
     *
     * The following is done on receiving this call:
     * <ul>
     <li>Find all interested subscribers.</li>
     <li>Invoke {@link EventCreator#createEvent(java.util.Set)} with <em>only</em> the event types that have atleast one listener.</li>
     <li>Apply all publisher level filters for this event. If any of the filter returns <code>false</code> then exit.</li>
     <li>Find all subscribers corresponding to the events created by the above call.</li>
     <li>For all subscribers, invoke the filters.</li>
     <li>For the subscribers that pass the filtering criterion, enqueue the event(s) to the subscriber's queue (async dispatch)</li>
     </ul>
     *
     * <b>This style of publishing must only be used if creating an event object is costly (either due to the size of the
     * object or work required to create such an object) otherwise, the complexity is not worth the effort.</b>
     *
     * @param creator Event creator to be invoked iff there is atleast one listener for the event types.
     * @param eventTypes Event types for which the events are to be published.
     */
    void publishIffNotDead(EventCreator creator, Class<?>... eventTypes);

    /**
     * Registers a subscriber which will be invoked iff the filter passes for the event to be published. This method
     * will attach the filter to all subscriber methods in the passed subscriber. If you need to attach different
     * filters to different methods, then you should register the subscriber without the filter
     * {@link EventBus#registerSubscriber(Object)} and then register each filter using
     * {@link EventBus#addFilterForSubscriber}
     *
     * See {@link EventBus} javadocs for details about subscribers, filters, event-subscriber association and dispatch
     * mode.
     *
     * @param filter Filter.
     * @param subscriber Subscriber to register.
     *
     * @throws InvalidSubscriberException If the passed subscriber is invalid.
     */
    void registerSubscriber(@Nullable EventFilter filter, Object subscriber) throws InvalidSubscriberException;

    /**
     * Registers a subscriber without any filters.
     * See {@link EventBus} javadocs for details about subscribers, filters, event-subscriber association and dispatch
     * mode.
     *
     * @param subscriber Subscriber to register.
     *
     * @throws InvalidSubscriberException If the passed subscriber is invalid.
     */
    void registerSubscriber(Object subscriber) throws InvalidSubscriberException;

    /**
     * Enables the {@link CatchAllSubscriber} for this eventbus with the sink for the subscriber as the passed
     * {@link BlockingQueue} instance.
     *
     * @param catchAllSink The sink for the {@link CatchAllSubscriber}
     *
     * @return <code>true</code> if the passes sink was successfully attached, iff there is no sink already present.
     */
    boolean enableCatchAllSubscriber(BlockingQueue<?> catchAllSink);

    /**
     * Disables the {@link CatchAllSubscriber} for this eventbus.
     */
    void disableCatchAllSubscriber();

    /**
     * Removes the subscriber class (all of its subscriber methods) from the event bus.
     * If there are more than one instance of the passed class registered with this event bus, all of them are removed.
     *
     * <b>All unconsumed events are rejected.</b>
     *
     * @param subscriberClass Subscriber class to unregister.
     *
     * @return Set of subscriber instances that were removed by this unregister. More than one subscriber instances
     * can be registered with this bus for the same subscriber class. In such a case, there will be more than one
     * instances in the list.
     */
    Set<Object> unregisterSubscriber(Class<?> subscriberClass);

    /**
     * Removes the subscriber instance (all of its subscriber methods) from the event bus.
     *
     * If there are other subscriber instances associated with the class of the passed subscriber, they are left
     * untouched. This should be the preferred mode of unregistration when more than one instances of the same subscriber
     * class are registered and only one is to be removed.
     *
     * <b>All unconsumed events are rejected.</b>
     *
     * @param subscriber Subscriber instance to unregister.
     *
     * @return <code>true</code> if the subscriber instance was unregistered, <code>false</code> otherwise.
     */
    boolean unregisterSubscriber(Object subscriber);

    /**
     * Adds the passed filter for the passed subscriber method.
     * See {@link EventBus} javadocs for details about subscribers &amp; filters.
     *
     * @param filter Filter to attach.
     * @param subscriberInfo Subscriber info. This can be retrieved by methods {@link EventBus#getAllSubscribersForAnEvent(Class)}
     *                       or {@link com.netflix.eventbus.spi.EventBus#getAllSubscribers()}
     */
    void addFilterForSubscriber(EventFilter filter, SubscriberInfo subscriberInfo);

    /**
     * Removes the passed filters for the passed subscriber.
     * See {@link EventBus} javadocs for details about subscribers &amp; filters.
     *
     * @param subscriberInfo Subscriber info. This can be retrieved by methods {@link EventBus#getAllSubscribersForAnEvent(Class)}
     *                       or {@link com.netflix.eventbus.spi.EventBus#getAllSubscribers()}
     * @param filters Filters to remove.
     */
    void removeFiltersForSubscriber(SubscriberInfo subscriberInfo, EventFilter... filters);

    /**
     * Removes all filters for the passed subscriber method.
     * See {@link EventBus} javadocs for details about subscribers &amp; filters.
     *
     * @param subscriberInfo Subscriber info. This can be retrieved by methods {@link EventBus#getAllSubscribersForAnEvent(Class)}
     *                       or {@link com.netflix.eventbus.spi.EventBus#getAllSubscribers()}
     */
    void clearFiltersForSubscriber(SubscriberInfo subscriberInfo);

    /**
     * Adds a publisher level filter for the passed event type.
     * See {@link EventBus} javadocs for details about publishers &amp; filters.

     * @param filter Filter
     * @param eventClass The class of the event for which the filter is to be attached.
     */
    void addFilterForEvent(EventFilter filter, Class<?> eventClass);

    /**
     * Removes the passed filters for the passed event type.
     * See {@link EventBus} javadocs for details about publishers &amp; filters.

     * @param eventClass The class of the event for which the filter are to be removed.
     * @param filters Filters to be removed
     */
    void removeFiltersForEvent(Class<?> eventClass, EventFilter... filters);

    /**
     * Removes all the filters for the passed event type.
     * See {@link EventBus} javadocs for details about publishers &amp; filters.

     * @param eventClass The class of the event for which the filter are to be cleared.
     */
    void clearFiltersForEvent(Class<?> eventClass);

    /**
     * Returns a set of subscribers that are registered with this event bus.
     *
     * The details of the filter
     * attached (if any) can be retrieved by the method {@link EventBus#getFilterForASubscriber}
     *
     * @return An immutable set of currently registered subscribers for the passed event type.
     */
    Set<SubscriberInfo> getAllSubscribers();

    /**
     * Returns a set of subscribers for that passed event type that are registered with this event bus.
     *
     * The details of the filter attached (if any) can be retrieved by the method
     * {@link EventBus#getFilterForASubscriber(SubscriberInfo)}
     *
     * @param eventType Event for which the subscribers are to be retrieved.
     *
     * @return An immutable set of currently registered subscribers.
     */
    Set<SubscriberInfo> getAllSubscribersForAnEvent(Class<?> eventType);

    /**
     * The set of event filters attached to the passed subscriber.
     *
     * @param subscriberInfo Subscriber info. This can be retrieved by methods {@link EventBus#getAllSubscribersForAnEvent(Class)}
     *                       or {@link com.netflix.eventbus.spi.EventBus#getAllSubscribers()}
     *
     * @return Immutable set of attached filters. Empty list if none exists.
     */
    Set<EventFilter> getFilterForASubscriber(SubscriberInfo subscriberInfo);

    /**
     * The set of event filters attached to the passed event type.
     *
     * @param eventType Event type for which the filters are to be retrieved.
     *
     * @return Immutable set of attached filters. Empty list if none exists.
     */
    Set<EventFilter> getFiltersForAnEvent(Class<?> eventType);

    /**
     * Returns all the event types which have atleast a subscriber or a filter defined in this event bus.
     *
     * @return Unmodifiable set of all event types registered with this event bus.
     */
    Set<Class<?>> getAllRegisteredEventTypes();
}
